/*
 * Copyright (C) 2011 The Android Open Source Project 
 * Copyright (C) 2013 Winson Wong
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * Source is base on Android SDK 16 Sample SoftKeyboard  
 */
package myidea.winson.android.cangjieime;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.Typeface;
import android.inputmethodservice.InputMethodService;
import android.media.AudioManager;
import android.os.Handler;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.text.InputType;
import android.text.method.MetaKeyKeyListener;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.View.OnTouchListener;
import android.view.MotionEvent;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.widget.PopupWindow;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

import myidea.winson.android.cangjieime.CangjieInput.CodeResult;

public class RelaxedCangjieKeyboard extends InputMethodService 
        implements KeyboardView.OnKeyboardActionListener, IKeyboardPrefChangedListener {
    static final boolean DEBUG = false;
    
    /**
     * This boolean indicates the optional example code for performing
     * processing of hard keys in addition to regular text generation
     * from on-screen interaction.  It would be used for input methods that
     * perform language translations (such as converting text entered on 
     * a QWERTY keyboard to Chinese), but may not be used for input methods
     * that are primarily intended to be used for on-screen text entry.
     */
    static final boolean PROCESS_HARD_KEYS = true;
    


    private InputMethodManager mInputMethodManager;

    private CangjieKeyboardView mInputView;
    private CandidateView mCandidateView;
    private CompletionInfo[] mCompletions;
    
    private StringBuilder mComposing = new StringBuilder();
    private CangjieComposing mCangjieComposing = new CangjieComposing();
    private boolean mPredictionOn;
    private boolean mCompletionOn;
    private int mLastDisplayWidth;
    //private boolean mCapsLock;
    private long mLastShiftTime;
    private long mMetaState;
    
    private CangjieKeyboard mSymbolsKeyboard;
    private CangjieKeyboard mSymbolsShiftedKeyboard;
    private CangjieKeyboard mQwertyKeyboard;
    private CangjieKeyboard mNumPadKeyboard;
    
    private CangjieKeyboard mCurKeyboard;
    
    private String mWordSeparators;
    
    private CangjieInput mCangjieInput;
    private PopupWindow mCangjieInputKeyCodePopup;
    private TextView mCangjieInputKeyCodeView;
    
    private CodeResult mCangjieCodeResult;
    private Preferences mPref;
    
    //private int mPreviousPickUpType = -1;
    
    private final int CHAR_TYPE_UNKNOWN = -1;
    private final int CHAR_TYPE_ENGLISH = 0;    
    private final int CHAR_TYPE_UNICODE = 1;
    
    private AudioManager mAudioManager;
    private Vibrator mVibrator;
    private String mPreviousSelectedString;
    
    private int[] mKeyboardViewCoordinates = new int[2];
    
    private int mCurrentCangjieInputKeyCodeDisplayPosition;
    
    /**
     * Main initialization of the input method component.  Be sure to call
     * to super class.
     */
    @Override public void onCreate() {
        super.onCreate();
        mInputMethodManager = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
        mAudioManager = (AudioManager)getSystemService(AUDIO_SERVICE);
        //mAudioManager.loadSoundEffects();
        mVibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
        mWordSeparators = getResources().getString(R.string.word_separators);
        mCangjieInput = new CangjieInput(this);
        mPref = Preferences.loadPreferences(this);
        mPref.registerKeyboardPrefChangedListener(this);
    }
    
    /**
     * This is the point where you can do all of your UI initialization.  It
     * is called after creation and any configuration change.
     */
    @Override public void onInitializeInterface() {
        if (mQwertyKeyboard != null) {
            // Configuration changes can happen after the keyboard gets recreated,
            // so we need to be able to re-build the keyboards if the available
            // space has changed.
            int displayWidth = getMaxWidth();
            if (displayWidth == mLastDisplayWidth) return;
            mLastDisplayWidth = displayWidth;
        }
        loadKeyboard();
        
    }
    
    /**
     * Called by the framework when your view for creating input needs to
     * be generated.  This will be called the first time your input method
     * is displayed, and every time it needs to be re-created such as due to
     * a configuration change.
     */
    @Override public View onCreateInputView() {
        mInputView = (CangjieKeyboardView) getLayoutInflater().inflate(
                R.layout.cangjie_keyboard_view, null);
        mInputView.setOnKeyboardActionListener(this);
        mInputView.setKeyboard(mQwertyKeyboard);
        mInputView.setProximityCorrectionEnabled(true);
        return mInputView;
    }

    /**
     * Called by the framework when your view for showing candidates needs to
     * be generated, like {@link #onCreateInputView}.
     */
    @Override public View onCreateCandidatesView() {
    	LayoutInflater inflate =
            (LayoutInflater) this
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    	
    	mCangjieInputKeyCodePopup = new PopupWindow(this);    	
    	mCangjieInputKeyCodeView = (TextView) inflate.inflate(R.layout.cangjie_preview_textview, null);
    	
    	mCangjieInputKeyCodePopup.setContentView(mCangjieInputKeyCodeView);
    	mCangjieInputKeyCodePopup.setBackgroundDrawable(null);
    	mCangjieInputKeyCodePopup.setTouchable(true);
    	
    	mCangjieInputKeyCodeView.setOnTouchListener(new OnTouchListener(){
    		@Override
    		public boolean onTouch(View v, MotionEvent event){
    			if (event.getActionMasked() == MotionEvent.ACTION_DOWN){
    				cangjieInputKeyCodePopupMove();
    			}
    			return true;
    		}
    	});
    	
        mCandidateView = new CandidateView(this);
        mCandidateView.setService(this);
        return mCandidateView;
    }

    /**
     * This is the main point where we do our initialization of the input method
     * to begin operating on an application.  At this point we have been
     * bound to the client, and are now receiving all of the detailed information
     * about the target of our edits.
     */
    @Override public void onStartInput(EditorInfo attribute, boolean restarting) {
        super.onStartInput(attribute, restarting);
        
        // Reset our state.  We want to do this even if restarting, because
        // the underlying state of the text editor could have changed in any way.
        mComposing.setLength(0);
        mCangjieComposing.clear();
        updateCandidates();
        
        closeCangjieInputKeyCodeDisplay();
       
        
        if (!restarting) {
            // Clear shift states.
            mMetaState = 0;
        }
        
        mPredictionOn = false;
        mCompletionOn = false;
        mCompletions = null;
        mCurrentCangjieInputKeyCodeDisplayPosition = mPref.cangjieInputKeyCodeDisplayPosition;
        
        // We are now going to initialize our state based on the type of
        // text being edited.
        switch (attribute.inputType & InputType.TYPE_MASK_CLASS) {
            case InputType.TYPE_CLASS_NUMBER:
            case InputType.TYPE_CLASS_DATETIME:
                // Numbers and dates default to the symbols keyboard, with
                // no extra features.
                mCurKeyboard = mNumPadKeyboard;
                break;
                
            case InputType.TYPE_CLASS_PHONE:
                // Phones will also default to the symbols keyboard, though
                // often you will want to have a dedicated phone keyboard.
                mCurKeyboard = mNumPadKeyboard;
                break;
                
            case InputType.TYPE_CLASS_TEXT:
                // This is general text editing.  We will default to the
                // normal alphabetic keyboard, and assume that we should
                // be doing predictive text (showing candidates as the
                // user types).
                mCurKeyboard = mQwertyKeyboard;
                mPredictionOn = true;
                
                // We now look for a few special variations of text that will
                // modify our behavior.
                int variation = attribute.inputType & InputType.TYPE_MASK_VARIATION;
                if (variation == InputType.TYPE_TEXT_VARIATION_PASSWORD ||
                        variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) {
                    // Do not display predictions / what the user is typing
                    // when they are entering a password.
                    mPredictionOn = false;
                }
                
                if (variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
                        || variation == InputType.TYPE_TEXT_VARIATION_URI
                        || variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
                    // Our predictions are not useful for e-mail addresses
                    // or URIs.
                    mPredictionOn = true;
                }
                
                if ((attribute.inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) {
                    // If this is an auto-complete text view, then our predictions
                    // will not be shown and instead we will allow the editor
                    // to supply their own.  We only show the editor's
                    // candidates when in fullscreen mode, otherwise relying
                    // own it displaying its own UI.
                    mPredictionOn = true;
                    mCompletionOn = isFullscreenMode();
                }
                
                // We also want to look at the current state of the editor
                // to decide whether our alphabetic keyboard should start out
                // shifted.
                updateShiftKeyState(attribute);
                break;
                
            default:
                // For all unknown input types, default to the alphabetic
                // keyboard with no special features.
                mCurKeyboard = mQwertyKeyboard;
                updateShiftKeyState(attribute);
        }
        
        // Update the label on the enter key, depending on what the application
        // says it will do.
        mCurKeyboard.setImeOptions(getResources(), attribute.imeOptions);
        
    }
 
    
    
    /**
     * This is called when the user is done editing a field.  We can use
     * this to reset our state.
     */
    @Override public void onFinishInput() {
    	super.onFinishInput();
        
    	//Log.i("SoftKeyboard","onFinishInput");
    	
        // Clear current composing text and candidates.
        mComposing.setLength(0);
        mCangjieComposing.clear();
        updateCandidates();
        closeCangjieInputKeyCodeDisplay();
        
        // We only hide the candidates window when finishing input on
        // a particular editor, to avoid popping the underlying application
        // up and down if the user is entering text into the bottom of
        // its window.
        setCandidatesViewShown(false);
        
        mCurKeyboard = mQwertyKeyboard;
        if (mInputView != null) {
        	mInputView.closing();        	
        }
        //requestHideSelf(0);
        
    }
    
    @Override public void onStartInputView(EditorInfo attribute, boolean restarting) {
        super.onStartInputView(attribute, restarting);
        // Apply the selected keyboard to the input view.
        mInputView.setKeyboard(mCurKeyboard);
        mInputView.closing();
        /*
        final InputMethodSubtype subtype = mInputMethodManager.getCurrentInputMethodSubtype();
        mInputView.setSubtypeOnSpaceKey(subtype);
        */
    }

    /*
    @Override
    public void onCurrentInputMethodSubtypeChanged(InputMethodSubtype subtype) {
        mInputView.setSubtypeOnSpaceKey(subtype);
    }
	*/
    
    /**
     * Deal with the editor reporting movement of its cursor.
     */
    @Override public void onUpdateSelection(int oldSelStart, int oldSelEnd,
            int newSelStart, int newSelEnd,
            int candidatesStart, int candidatesEnd) {
        super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
                candidatesStart, candidatesEnd);
        
        // If the current selection in the text view changes, we should
        // clear whatever candidate text we have.
        if (mComposing.length() > 0 && (newSelStart != candidatesEnd
                || newSelEnd != candidatesEnd)) {
            mComposing.setLength(0);
            mCangjieComposing.clear();
            updateCandidates();
            closeCangjieInputKeyCodeDisplay();
            InputConnection ic = getCurrentInputConnection();
            if (ic != null) {
                ic.finishComposingText();
            }
        }
    }

    /**
     * This tells us about completions that the editor has determined based
     * on the current text in it.  We want to use this in fullscreen mode
     * to show the completions ourself, since the editor can not be seen
     * in that situation.
     */
    @Override public void onDisplayCompletions(CompletionInfo[] completions) {
        if (mCompletionOn) {
            mCompletions = completions;
            if (completions == null) {
                setSuggestions(null, -1, -1, -1, -1, false);
                return;
            }
            
            List<String> stringList = new ArrayList<String>();
            for (int i = 0; i < completions.length; i++) {
                CompletionInfo ci = completions[i];
                if (ci != null) stringList.add(ci.getText().toString());
            }
            setSuggestions(stringList, stringList.size()-1, -1, -1, 0, false);
        }
    }
    
    @Override public boolean onEvaluateFullscreenMode(){
    	if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE){
    		switch(mPref.forcesRunFullscreen){
    		case Preferences.FRF_SYSTEM:
    			return super.onEvaluateFullscreenMode();
			case Preferences.FRF_NO:
    			return false;
			case Preferences.FRF_YES:
    			return true;
    		}
    	}
   		return super.onEvaluateFullscreenMode();
    }
    
    @Override public void onDestroy(){
    	onFinishInput();
    	super.onDestroy();
    	
    	if (mCangjieInput != null){
    		this.mCangjieInput.destory();
    	}
    }
    
    /**
     * This translates incoming hard key events in to edit operations on an
     * InputConnection.  It is only needed when using the
     * PROCESS_HARD_KEYS option.
     */
    private boolean translateKeyDown(int keyCode, KeyEvent event) {
        mMetaState = MetaKeyKeyListener.handleKeyDown(mMetaState,
                keyCode, event);
        int c = event.getUnicodeChar(MetaKeyKeyListener.getMetaState(mMetaState));
        mMetaState = MetaKeyKeyListener.adjustMetaAfterKeypress(mMetaState);
        InputConnection ic = getCurrentInputConnection();
        if (c == 0 || ic == null) {
            return false;
        }
        

        if ((c & KeyCharacterMap.COMBINING_ACCENT) != 0) {
            c = c & KeyCharacterMap.COMBINING_ACCENT_MASK;
        }
        
        if (mCangjieComposing.length() > 0) {
            char accent = mCangjieComposing.charAt(mCangjieComposing.length() -1 );
            int composed = KeyEvent.getDeadChar(accent, c);

            if (composed != 0) {
                c = composed;
                //mComposing.setLength(mComposing.length()-1);
                mCangjieComposing.setLength(mCangjieComposing.length()-1);
            }
        }
        
        onKey(c, null);
        
        return true;
    }
    
    /**
     * Use this to monitor key events being delivered to the application.
     * We get first crack at them, and can either resume them or let them
     * continue to the app.
     */
    @Override public boolean onKeyDown(int keyCode, KeyEvent event) {
        switch (keyCode) {
            case KeyEvent.KEYCODE_BACK:
                // The InputMethodService already takes care of the back
                // key for us, to dismiss the input method if it is shown.
                // However, our keyboard could be showing a pop-up window
                // that back should dismiss, so we first allow it to do that.
                if (event.getRepeatCount() == 0 && mInputView != null) {
                    if (mInputView.handleBack()) {
                        return true;
                    }
                }
                break;
                
            case KeyEvent.KEYCODE_DEL:
                // Special handling of the delete key: if we currently are
                // composing text for the user, we want to modify that instead
                // of let the application to the delete itself.
                if (mCangjieComposing.length() > 0) {
                    onKey(Keyboard.KEYCODE_DELETE, null);
                    return true;
                }
                break;
                
            case KeyEvent.KEYCODE_ENTER:
                // Let the underlying text editor always handle these.
                return false;
                
            default:
                // For all other keys, if we want to do transformations on
                // text being entered with a hard keyboard, we need to process
                // it and do the appropriate action.
                if (PROCESS_HARD_KEYS) {
                    if (keyCode == KeyEvent.KEYCODE_SPACE
                            && (event.getMetaState()&KeyEvent.META_ALT_ON) != 0) {
                        // A silly example: in our input method, Alt+Space
                        // is a shortcut for 'android' in lower case.
                        InputConnection ic = getCurrentInputConnection();
                        if (ic != null) {
                            // First, tell the editor that it is no longer in the
                            // shift state, since we are consuming this.
                            ic.clearMetaKeyStates(KeyEvent.META_ALT_ON);
                            keyDownUp(KeyEvent.KEYCODE_A);
                            keyDownUp(KeyEvent.KEYCODE_N);
                            keyDownUp(KeyEvent.KEYCODE_D);
                            keyDownUp(KeyEvent.KEYCODE_R);
                            keyDownUp(KeyEvent.KEYCODE_O);
                            keyDownUp(KeyEvent.KEYCODE_I);
                            keyDownUp(KeyEvent.KEYCODE_D);
                            // And we consume this event.
                            return true;
                        }
                    }
                    if (mPredictionOn && translateKeyDown(keyCode, event)) {
                        return true;
                    }
                }
        }
        
        return super.onKeyDown(keyCode, event);
    }

    /**
     * Use this to monitor key events being delivered to the application.
     * We get first crack at them, and can either resume them or let them
     * continue to the app.
     */
    @Override public boolean onKeyUp(int keyCode, KeyEvent event) {
        // If we want to do transformations on text being entered with a hard
        // keyboard, we need to process the up events to update the meta key
        // state we are tracking.
        if (PROCESS_HARD_KEYS) {
            if (mPredictionOn) {
                mMetaState = MetaKeyKeyListener.handleKeyUp(mMetaState,
                        keyCode, event);
            }
        }
        
        return super.onKeyUp(keyCode, event);
    }

    /**
     * Helper function to commit any text being composed in to the editor.
     */
    private void commitTyped(InputConnection inputConnection) {
        if (mComposing.length() > 0) {
            inputConnection.commitText(mComposing, mComposing.length());
            closeCangjieInputKeyCodeDisplay();  
            mComposing.setLength(0);
            mCangjieComposing.clear();
            updateCandidates();
        }
    }

    /**
     * Helper to update the shift state of our keyboard based on the initial
     * editor state.
     */
    private void updateShiftKeyState(EditorInfo attr) {
        if (attr != null 
                && mInputView != null && mQwertyKeyboard == mInputView.getKeyboard()) {
            int caps = 0;
            EditorInfo ei = getCurrentInputEditorInfo();
            if (ei != null && ei.inputType != InputType.TYPE_NULL) {
                caps = getCurrentInputConnection().getCursorCapsMode(attr.inputType);
            }
            mInputView.setShifted(mInputView.getCapsLock() || caps != 0);
        }
    }
    
    /**
     * Helper to determine if a given character code is alphabetic.
     */
    private boolean isAlphabet(int code) {
        if (Character.isLetter(code)) {
            return true;
        } else {
            return false;
        }
    }
    
    /**
     * Helper to send a key down / key up pair to the current editor.
     */
    private void keyDownUp(int keyEventCode) {
        getCurrentInputConnection().sendKeyEvent(
                new KeyEvent(KeyEvent.ACTION_DOWN, keyEventCode));
        getCurrentInputConnection().sendKeyEvent(
                new KeyEvent(KeyEvent.ACTION_UP, keyEventCode));
    }
    
    /**
     * Helper to send a character to the editor as raw key events.
     */
    private void sendKey(int keyCode) {
        switch (keyCode) {
            case '\n':
                keyDownUp(KeyEvent.KEYCODE_ENTER);
                break;
            case Keyboard.KEYCODE_MOVE_UP:
            	keyDownUp(KeyEvent.KEYCODE_DPAD_UP);
            	break;
            case Keyboard.KEYCODE_MOVE_DOWN:
            	keyDownUp(KeyEvent.KEYCODE_DPAD_DOWN);
            	break;
            case Keyboard.KEYCODE_MOVE_RIGHT:
            	keyDownUp(KeyEvent.KEYCODE_DPAD_RIGHT);
            	break;
            case Keyboard.KEYCODE_MOVE_LEFT:
            	keyDownUp(KeyEvent.KEYCODE_DPAD_LEFT);
            	break;
            default:
                if (keyCode >= '0' && keyCode <= '9') {
                    keyDownUp(keyCode - '0' + KeyEvent.KEYCODE_0);                
                } else {
                    getCurrentInputConnection().commitText(String.valueOf((char) keyCode), 1);
                }
                break;
        }
    }
    
    private void pasteShortCut(String shortCutString){
    	// Handle separator
    	int pickIndex = -1;
    	/* if keyboard have some uncommitted key, pick the default character from candidate view */
        if (mComposing.length() > 0) {
        	if (mPref.isSymbolsPickupChinese){
        		pickIndex = this.pickDefaultCandidate();
        	}
        	else{
        		commitTyped(getCurrentInputConnection());
        	}                
        }
        else{
        	mCandidateView.clear();
        }
    	getCurrentInputConnection().commitText(shortCutString, 1);
    	updateShiftKeyState(getCurrentInputEditorInfo());
    }

    // Implementation of KeyboardViewListener

    public void onKey(int primaryCode, int[] keyCodes) {
        if (isWordSeparator(primaryCode)) {
            // Handle separator
        	int pickIndex = -1;
        	/* if keyboard have some uncommitted key, pick the default character from candidate view */
            if (mCangjieComposing.length() > 0) {
            	if (mPref.isSymbolsPickupChinese){
            		pickIndex = this.pickDefaultCandidate();
            	}
            	else{
            		commitTyped(getCurrentInputConnection());
            	}                
            }
            else{
            	mCandidateView.clear();
            }
            /* if this is not space or picking up Chinese, send out the key code */ 
            if (primaryCode != 32 || pickIndex != 1){
                sendKey(primaryCode);            	
            }
            updateShiftKeyState(getCurrentInputEditorInfo());
        } else if (primaryCode == Keyboard.KEYCODE_DELETE) {
            handleBackspace();
        } else if (primaryCode == Keyboard.KEYCODE_SHIFT) {
            handleShift();
        } else if (primaryCode == Keyboard.KEYCODE_CANCEL) {
            handleClose();
            return;
        } else if (primaryCode == CangjieKeyboardView.KEYCODE_OPTIONS) {
            //show menu allow user selected another IME
        	this.mInputMethodManager.showInputMethodPicker();
        	//this.mInputMethodManager.toggleSoftInput(0, 0);
        } else if (primaryCode == Keyboard.KEYCODE_MODE_CHANGE
                && mInputView != null) {
            Keyboard current = mInputView.getKeyboard();
            if (current == mSymbolsKeyboard || current == mSymbolsShiftedKeyboard) {
                current = mQwertyKeyboard;
            } else {
                current = mSymbolsKeyboard;
            }
            closeCangjieInputKeyCodeDisplay();
            mInputView.setKeyboard(current);
            if (current == mSymbolsKeyboard) {
                current.setShifted(false);
            }
        } else if (primaryCode == Keyboard.KEYCODE_WWW){
        	pasteShortCut("www.");
        } else if (primaryCode == Keyboard.KEYCODE_COM){
        	pasteShortCut(".com");
        } else if (primaryCode == Keyboard.KEYCODE_EDU){
        	pasteShortCut(".edu");
        } else if (primaryCode == Keyboard.KEYCODE_ORG){
        	pasteShortCut(".org");
        } else if (primaryCode == Keyboard.KEYCODE_NET){
        	pasteShortCut(".net");
        } else if (primaryCode == Keyboard.KEYCODE_HK){
        	pasteShortCut(".hk");
        } else if (primaryCode == Keyboard.KEYCODE_CN){
        	pasteShortCut(".cn");
        } else if (primaryCode == Keyboard.KEYCODE_TW){
        	pasteShortCut(".tw");
        } else if (primaryCode == Keyboard.KEYCODE_MY){
        	pasteShortCut(".my");
        } else if (primaryCode == Keyboard.KEYCODE_MO){
        	pasteShortCut(".mo");
        } else if (primaryCode == Keyboard.KEYCODE_MOVE_UP || primaryCode == Keyboard.KEYCODE_MOVE_DOWN || primaryCode == Keyboard.KEYCODE_MOVE_LEFT || primaryCode == Keyboard.KEYCODE_MOVE_RIGHT ){
        	updateCangjieKeyCodeDisplay("");
            mComposing.setLength(0);
            mCangjieComposing.clear();
            getCurrentInputConnection().commitText("", 0);
            updateCandidates();
            sendKey(primaryCode);
        } else {        	
            handleCharacter(primaryCode, keyCodes);
        }
    }

    public void onText(CharSequence text) {
        InputConnection ic = getCurrentInputConnection();
        if (ic == null) return;
        ic.beginBatchEdit();
        if (mComposing.length() > 0) {
            commitTyped(ic);
        }
        ic.commitText(text, 0);
        ic.endBatchEdit();
        updateShiftKeyState(getCurrentInputEditorInfo());
    }

    /**
     * Update the list of available candidates from the current composing
     * text.  This will need to be filled in by however you are determining
     * candidates.
     */
    private void updateCandidates() {
        if (!mCompletionOn) {
            if (mCangjieComposing.length() > 0) {
            	String composing = mCangjieComposing.toString();
                ArrayList<String> list = new ArrayList<String>();
                list.add(composing);
                
                if (mPref.enableCangjieAutoCorrect){
                	mCangjieCodeResult = mCangjieInput.getCode(mCangjieComposing,mCandidateView.MAX_SUGGESTIONS-1);
                }
                else{
                	mCangjieCodeResult = mCangjieInput.getCode(mCangjieComposing.toString(),mCandidateView.MAX_SUGGESTIONS-1);
                }
            	String cangjieCode = mCangjieCodeResult.returnResult;
                if (cangjieCode != null){
                	char[] cangjieCodeChr = cangjieCode.toCharArray();
                	for (char c : cangjieCodeChr){
                		list.add(String.valueOf(c));
                	}
                }
                int defaultIndex;
                switch(mPref.defaultPickupMode){
                case Preferences.DPM_ENGLISH_FIRST :
                	defaultIndex = 0;
                	break;
                case Preferences.DPM_CHINESE_FIRST :
                	defaultIndex = 1;
                	break;
                case Preferences.DPM_INTELLIGENT_CHINESE_FIRST :
                	switch(getBeforeCursorCharacterType()){
                	case CHAR_TYPE_ENGLISH:
                		defaultIndex = 0;
                		break;
                	case CHAR_TYPE_UNICODE:
                	default :
                		defaultIndex = 1;
                		break;
                	}
                	break;
                case Preferences.DPM_INTELLIGENT_ENGLISH_FIRST :
                	switch(getBeforeCursorCharacterType()){
                	case CHAR_TYPE_UNICODE:
                		defaultIndex = 1;
                		break;
                	case CHAR_TYPE_ENGLISH:
                	default :
                		defaultIndex = 0;
                		break;
                	}
                	break;
                default:
                	defaultIndex = 0;
                }
                setSuggestions(list, 0, mCangjieCodeResult.matchedLastIndex+1, mCangjieCodeResult.errorCorrectionLastIndex+1, defaultIndex,(mPref.mixInputDisplayMode==Preferences.MID_DEFAULT_FIRST_WITHOUT_CANDIDATE));
            } else {
                setSuggestions(null, -1, -1, -1, -1, false);
            }
        }
    }
    
    private int getBeforeCursorCharacterType(){
    	int beforeCursorCount = 2;
    	if (mComposing != null){
    		beforeCursorCount += mComposing.length();
    	}
    	CharSequence cs = getCurrentInputConnection().getTextBeforeCursor(beforeCursorCount, 0);
    	if (cs == null || cs.length() == 0 || cs.length() == mComposing.length()){
    		return CHAR_TYPE_UNKNOWN;
    	}
    	//Log.i("SoftKeyboard","cs : " + cs);
    	//Log.i("SoftKeyboard","Pre Chr : " + String.valueOf(cs.charAt(0)) + " : " + String.valueOf((int)cs.charAt(0)));
    	
    	for(int i=cs.length()-mComposing.length()-1;i>=0;i--){
    		char c = cs.charAt(i);
    		//Log.i("SoftKeyboard","Analysis Chr : " + String.valueOf(c) + " : " + String.valueOf((int)c) + " : " + String.valueOf(Character.isLetter(c)));
    		if ((int) c > 127 ){
        		return CHAR_TYPE_UNICODE;
        	}
        	else if(Character.isLetter(c)){
        		return CHAR_TYPE_ENGLISH;
        	}
        	else if((int) c == 10){
        		return CHAR_TYPE_UNKNOWN;
        	}
    	}
    	return CHAR_TYPE_UNKNOWN;
    	
    	
    }
    
    public void setSuggestions(List<String> suggestions,
            int typedEngWordIndex, int typedChiWordValidLastIndex, int typedChiWordErrorCorrectionLastIndex, int defaultIndex, boolean isIgnoreDefault) {
        if (suggestions != null && suggestions.size() > 0) {
            setCandidatesViewShown(true);
        } else if (isExtractViewShown()) {
            setCandidatesViewShown(true);
        } else {
        	setCandidatesViewShown(false);
        }
        if (mCandidateView != null) {
            mCandidateView.setSuggestions(suggestions, typedEngWordIndex, typedChiWordValidLastIndex, typedChiWordErrorCorrectionLastIndex, defaultIndex, isIgnoreDefault);
        }
    }
    
    private void closeCangjieInputKeyCodeDisplay(){
    	if (mCangjieInputKeyCodePopup != null && mCangjieInputKeyCodePopup.isShowing()) {
			mCangjieInputKeyCodePopup.dismiss();
        }
    }
    
   private void updateCangjieKeyCodeDisplay(String cangjieKeyCode){
    	if (cangjieKeyCode.length() > 0 && mCurrentCangjieInputKeyCodeDisplayPosition != Preferences.CIKCDP_NO_SHOW){
    		//Log.i("SoftKeyboard","Try show cangjie code : " + cangjieKeyCode);
    		mCangjieInputKeyCodeView.setCompoundDrawables(null, null, null, null);
    		mCangjieInputKeyCodeView.setText(cangjieKeyCode);
    		mCangjieInputKeyCodeView.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 
                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
    		final int popupWidth = mCangjieInputKeyCodeView.getMeasuredWidth();
    		final int popupHeight = mCangjieInputKeyCodeView.getMeasuredHeight();
    		int popupX ;
    		switch(mCurrentCangjieInputKeyCodeDisplayPosition){
    			case Preferences.CIKCDP_LEFT:
    				popupX= 0;
    				break;
    			case Preferences.CIKCDP_RIGHT:
    				popupX = this.mInputView.getWidth()-popupWidth;
    				break;
    			case Preferences.CIKCDP_MIDDLE:
    				popupX = (this.mInputView.getWidth()-popupWidth)/2;
    				break;
    			default :
    				popupX = 0;
    		}
    		
    		int popupY = popupHeight*-1;
    		
    		
    		//this.setCandidatesViewShown(true);
    		mInputView.getLocationInWindow(mKeyboardViewCoordinates);
    		popupX += mKeyboardViewCoordinates[0];
    		popupY += mKeyboardViewCoordinates[1];
    		//popupY -= mCandidateView.getMeasuredHeight();
    		popupY -= mCandidateView.getHeight();
    		
    		if (mCangjieInputKeyCodePopup.isShowing()) {    			
    			mCangjieInputKeyCodePopup.update(popupX, popupY,
                        popupWidth, popupHeight);
            } else {
            	mCangjieInputKeyCodePopup.setWidth(popupWidth);
            	mCangjieInputKeyCodePopup.setHeight(popupHeight);            	
            	//Log.i("RelaxedCangjieKeyboard","X=" + String.valueOf(popupX) + ",Y=" + String.valueOf(popupY) + ",PlaceAt=" + mInputView.toString());
            	mCangjieInputKeyCodePopup.showAtLocation(mInputView, Gravity.NO_GRAVITY, 
            			popupX, popupY);
            	
            }
    		mCangjieInputKeyCodeView.setVisibility(View.VISIBLE);
    	}
    	else{
    		if (mCangjieInputKeyCodePopup.isShowing()) {
    			mCangjieInputKeyCodePopup.dismiss();
            }
    	}
    	
    }
    
    private void handleBackspace() {
        final int length = mCangjieComposing.length();
        if (length > 1) {
            //mComposing.delete(length - 1, length);
            mCangjieComposing.delete(length-1, length);
            String cangjieKeyCode;
            /*
            if (mCangjieComposing.length()>5){
            	cangjieKeyCode = mCurKeyboard.getCangjieCode(mComposing.substring(0, 5));
            }
            else{
            	cangjieKeyCode = mCurKeyboard.getCangjieCode(mComposing.toString());
            }
            updateCangjieKeyCodeDisplay(cangjieKeyCode);
            */
            
            if (mCangjieComposing.length()<5){
            	cangjieKeyCode = mCurKeyboard.getCangjieCode(mCangjieComposing.toString());
                updateCangjieKeyCodeDisplay(cangjieKeyCode);
            }
            
            if (mPref.mixInputDisplayMode == Preferences.MID_ENGLISH_FIRST){
            	mComposing.setLength(0);
            	mComposing.append(mCangjieComposing.toString());
           		getCurrentInputConnection().setComposingText(mComposing.toString(), 1);
           	}
            updateCandidates();
            if (mPref.mixInputDisplayMode != Preferences.MID_ENGLISH_FIRST){
            	mComposing.setLength(0);
            	mComposing.append(mCandidateView.getSuggestion(mCandidateView.getDefaultIndex()));
            	getCurrentInputConnection().setComposingText(mComposing.toString() , 1);
            }
        } else if (length > 0) {
        	updateCangjieKeyCodeDisplay("");
            //mComposing.setLength(0);
            mCangjieComposing.clear();
            getCurrentInputConnection().commitText("", 0);
            updateCandidates();
        } else {
        	mCandidateView.clear();
            keyDownUp(KeyEvent.KEYCODE_DEL);
        }
        updateShiftKeyState(getCurrentInputEditorInfo());
    }

    private void handleShift() {
        if (mInputView == null) {
            return;
        }
        
        Keyboard currentKeyboard = mInputView.getKeyboard();
        if (mQwertyKeyboard == currentKeyboard) {
            // Alphabet keyboard
            checkToggleCapsLock();
            mInputView.setShifted(mInputView.getCapsLock() || !mInputView.isShifted());
        } else if (currentKeyboard == mSymbolsKeyboard) {
            mSymbolsKeyboard.setShifted(true);
            mInputView.setKeyboard(mSymbolsShiftedKeyboard);
            mSymbolsShiftedKeyboard.setShifted(true);
        } else if (currentKeyboard == mSymbolsShiftedKeyboard) {
            mSymbolsShiftedKeyboard.setShifted(false);
            mInputView.setKeyboard(mSymbolsKeyboard);
            mSymbolsKeyboard.setShifted(false);
        }
    }
    
    private void handleCharacter(int primaryCode, int[] keyCodes) {
        if (isInputViewShown()) {
            if (mInputView.isShifted()) {
                primaryCode = Character.toUpperCase(primaryCode);
            }
        }
        if (isAlphabet(primaryCode) && mPredictionOn) {
        	/*
        	if (keyCodes != null){
        		if (keyCodes[1] != -1){
        			StringBuilder logStr = new StringBuilder();
            		for(int c : keyCodes){
            			if (c != -1){
            				logStr.append((char)c);
            			}
            		}	
            		Log.i("SoftKeyboard",logStr.toString() + "<------------inaccurate input");
        		}
        		
        	}
        	*/
        	
            //mComposing.append((char) primaryCode);
            mCangjieComposing.append((char) primaryCode, keyCodes, null);
            
            //display cangjie code user typed. 
            String cangjieKeyCode;
            /*
            if (mCangjieComposing.length()>5){
            	cangjieKeyCode = mCurKeyboard.getCangjieCode(mComposing.substring(0, 5));
            }
            else{
            	cangjieKeyCode = mCurKeyboard.getCangjieCode(mComposing.toString());
            }
           	updateCangjieKeyCodeDisplay(cangjieKeyCode);
           	*/
           	
           	if(mCangjieComposing.length()<=5){
           		cangjieKeyCode = mCurKeyboard.getCangjieCode(mCangjieComposing.toString());
           		updateCangjieKeyCodeDisplay(cangjieKeyCode);
           	}
           	
           	
           	if (mPref.mixInputDisplayMode == Preferences.MID_ENGLISH_FIRST){
           		mComposing.setLength(0);
           		mComposing.append(mCangjieComposing.toString());
           		getCurrentInputConnection().setComposingText(mComposing.toString(), 1);
                updateShiftKeyState(getCurrentInputEditorInfo());
           	}
           	
            updateCandidates();
            
            if (mPref.mixInputDisplayMode != Preferences.MID_ENGLISH_FIRST){
            	mComposing.setLength(0);
           		
           		if (mPref.isTraditionToSimplified){
            		mComposing.append(mCangjieInput.getTToS(mCandidateView.getSuggestion(mCandidateView.getDefaultIndex())));
            	}
           		else{
               		mComposing.append(mCandidateView.getSuggestion(mCandidateView.getDefaultIndex()));           			
           		}
           		getCurrentInputConnection().setComposingText(mComposing.toString() , 1);
                updateShiftKeyState(getCurrentInputEditorInfo());
            }
            
        } else {
            getCurrentInputConnection().commitText(
                    String.valueOf((char) primaryCode), 1);
            
        }
    }

    private void handleClose() {
        commitTyped(getCurrentInputConnection());
        requestHideSelf(0);
        mInputView.closing();
    }

    private void checkToggleCapsLock() {
        long now = System.currentTimeMillis();
        if (mLastShiftTime + 400 > now) {
            mInputView.setCapsLock(!mInputView.getCapsLock());
            mLastShiftTime = 0;
        } else {
            mLastShiftTime = now;
        }
    }
    
    
    public boolean isWordSeparator(int code) {
        return mWordSeparators.contains(String.valueOf((char)code));
    }
 
    public int pickDefaultCandidate() {
    	int defaultIndex = mCandidateView.getDefaultIndex();
        pickSuggestionManually(defaultIndex);
    	return defaultIndex;
    }
    
    public void pickSuggestionManually(int index) {
    	//Log.i("Softkeyboard","pickSuggextionManually");
        if (mCompletionOn && mCompletions != null && index >= 0
                && index < mCompletions.length) {
            CompletionInfo ci = mCompletions[index];
            getCurrentInputConnection().commitCompletion(ci);
            closeCangjieInputKeyCodeDisplay();
            if (mCandidateView != null) {                
                mCandidateView.clear();
            }
            updateShiftKeyState(getCurrentInputEditorInfo());
            //mPreviousPickUpType=-1;
        } else if (/*mComposing.length() > 0 && */index >= 0) {
            // If we were generating candidate suggestions for the current
            // text, we would commit one of them here.  But for this sample,
            // we will just commit the current text.
        	String pickupWord =  mCandidateView.getSuggestion(index);
        	/* if selected a Chinese character, update the selection order (it does not support character selected by word selection yet)*/
        	if (index > 0 && mCangjieCodeResult != null){
            	String inputKey = mCangjieCodeResult.getKey(index-1);
            	if (index - 1 <= mCangjieCodeResult.matchedLastIndex){
            		mCangjieInput.updateOrder(pickupWord,inputKey, true, mCandidateView.MAX_SUGGESTIONS-1);        			
        		}
        		else if (index - 1 <= mCangjieCodeResult.errorCorrectionLastIndex ){
        			mCangjieInput.updateOrder(pickupWord,inputKey, true, mCandidateView.MAX_SUGGESTIONS-1);
        		}
        		else{
        			mCangjieInput.updateOrder(pickupWord, inputKey, false, mCandidateView.MAX_SUGGESTIONS-1);
        		}
        	}
        	mCangjieCodeResult = null;
        	mComposing.setLength(0);
        	mCangjieComposing.clear();
        	//Log.i("RelaxedCangjieKeyboard",String.valueOf(mPref.isTraditionToSimplified));
        	if (mPref.isTraditionToSimplified){
        		mComposing.append(mCangjieInput.getTToS(pickupWord));
        	}else{
        		mComposing.append(pickupWord);
        	}
        		
            commitTyped(getCurrentInputConnection());

            if (mPref.enableRelatedChineseWord){
	            /*if pickup word is Chinese and before character same as previous pickup word, save it to word DB*/
	            if (pickupWord.length() == 1 && (int)pickupWord.charAt(0) > 127  && mPreviousSelectedString != null){
	            	CharSequence txtBeforeCursor = this.getCurrentInputConnection().getTextBeforeCursor(2, 0); //get 2 char since the selected word is committed */
	            	//Log.i("Softkeyboard","txtBeforeCursro = " + txtBeforeCursor.toString());
	            	//Log.i("Softkeyboard","mPreviousSelectedString = " + mPreviousSelectedString);
	            	//Log.i("Softkeyboard","isequals = " + String.valueOf(txtBeforeCursor.subSequence(0, 1).equals(mPreviousSelectedString)));
	            	/* before cursor get 2 char and (the first char = previous pickup char or (tran2Simp is enabled and the first char = previous pickup char simplified version))*/
	            	if (txtBeforeCursor != null && txtBeforeCursor.length() == 2 && (
	            			txtBeforeCursor.subSequence(0, 1).equals(mPreviousSelectedString) ||
	            			(mPref.isTraditionToSimplified && txtBeforeCursor.subSequence(0, 1).equals(mCangjieInput.getTToS(mPreviousSelectedString)))
	            		)
	            			
	            	){
	            		//Log.i("Softkeyboard","saveWord");
	                	this.mCangjieInput.saveWord(mPreviousSelectedString, pickupWord, mPref.defaultPickupMaxSize);            		
	            	}            
	            }
	            
	        	String fWord = this.mCangjieInput.getWord(pickupWord);
	        	//Log.i("Softkeyboard","fWord = " + fWord);
	        	if (fWord != null){
	        		ArrayList<String> suggestions = new ArrayList<String>();
	        		int fWordLength = fWord.length();
	        		for (int i=0;i<fWordLength;i++){       			
	        			suggestions.add(fWord.substring(i,i+1));
	        		}
	        		setSuggestions(suggestions,-1,suggestions.size()-1,-1,0,false);
	        	}

            }
            
        	
        	mPreviousSelectedString = pickupWord;
        }
    }
    
    public void swipeRight() {
       handleShift();
    }
    
    public void swipeLeft() {
        handleBackspace();
    }

    public void swipeDown() {
        handleClose();
    }

    public void swipeUp() {
    	pickDefaultCandidate();
    }
    
    public void onPress(int primaryCode) {
    	if(mPref.enableKeySound){
    		soundKey(primaryCode);
    	}
    	
    	if(mPref.enableKeyVibrate){
    		vibrateKey(primaryCode);
    	}
    	
    }
    
    public void onRelease(int primaryCode) {
    }
    
    private void soundKey(int primaryCode){
    	int effectType;
    	switch(primaryCode){
    	case -5:
    		effectType = AudioManager.FX_KEYPRESS_DELETE;
    		break;
    	case 10:
    		effectType = AudioManager.FX_KEYPRESS_RETURN;
    		break;
    	case 32:
    		effectType = AudioManager.FX_KEYPRESS_SPACEBAR;
    		break;
    	default:
    		effectType = AudioManager.FX_KEYPRESS_STANDARD;
    	}
    	mAudioManager.playSoundEffect(effectType, -1);
    }
    
    private void vibrateKey(int primaryCode){
    	mVibrator.vibrate(30);
    }

	@Override
	public void keyboardPrefChanged() {
		loadKeyboard();
	}

	private void loadKeyboard() {
		if (mPref.isKeyboardFiveRowMode){
			mQwertyKeyboard = new CangjieKeyboard(this, R.xml.qwerty5,mPref.keyboardKeySizeMode);
			mSymbolsKeyboard = new CangjieKeyboard(this, R.xml.symbols5,mPref.keyboardKeySizeMode);
			mSymbolsShiftedKeyboard = new CangjieKeyboard(this, R.xml.symbols_shift5,mPref.keyboardKeySizeMode);
		}
		else{
			mQwertyKeyboard = new CangjieKeyboard(this, R.xml.qwerty,mPref.keyboardKeySizeMode);
			mSymbolsKeyboard = new CangjieKeyboard(this, R.xml.symbols,mPref.keyboardKeySizeMode);
			mSymbolsShiftedKeyboard = new CangjieKeyboard(this, R.xml.symbols_shift,mPref.keyboardKeySizeMode);			
		}
        mNumPadKeyboard = new CangjieKeyboard(this, R.xml.numpad,mPref.keyboardKeySizeMode);
	}
    
	private void cangjieInputKeyCodePopupMove(){		
		//Log.i("RelaxedCangjieKeyboard","window touch 2");
		
		switch(mCurrentCangjieInputKeyCodeDisplayPosition){
		case Preferences.CIKCDP_LEFT :
			mCurrentCangjieInputKeyCodeDisplayPosition = Preferences.CIKCDP_MIDDLE;
			break;
		case Preferences.CIKCDP_MIDDLE :
			mCurrentCangjieInputKeyCodeDisplayPosition = Preferences.CIKCDP_RIGHT;
			break;
		case Preferences.CIKCDP_RIGHT :
			mCurrentCangjieInputKeyCodeDisplayPosition = Preferences.CIKCDP_LEFT;
			break;
		}
		
   		String cangjieKeyCode = mCurKeyboard.getCangjieCode(mCangjieComposing.toString());
   		updateCangjieKeyCodeDisplay(cangjieKeyCode);
	}
	
    /*
    final Handler mHandler = new Handler();
	// Create runnable for posting
	final Runnable mShowCandidateView = new Runnable() {
		public void run() {
			setCandidatesViewShown(true);	
	    	}
	};
	final Runnable mHideCandidateView = new Runnable() {
		public void run() {
				setCandidatesViewShown(false);	
	
	    }
	};
	*/
}
